/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* smutil.c ... data entry, selection menus & other utilities */

#define SM_CLASS extern
#include "smlib.h"
#include "smio.h"

CHAR *smutil_rcsid(void) {
    return("$Id: smutil.c,v 1.6 2008/02/19 13:15:50 bhepple Exp $");
}

#ifdef __STDC__
extern CHAR *re_comp(CHAR *),
    	    *regex(CHAR *),
	    *bopat[],
    	    *eopat[];
#else
extern CHAR *re_comp(),
    	    *regex(),
	    *bopat[],
    	    *eopat[];
#endif

static struct {
    CHAR	yank[SM_MAXREC];
    int		ignore_case;
    CHAR 	regular_exp[100];
    CHAR 	substitute[100];
} global = {
    "",
    1,
    "",
    ""
};

static INDEX sm_findit ARG6(CHAR *, 	pattern, 
			   CHAR *, 	string, 
			   INDEX, 	index, 
			   SM_FIELD, 	search_field, 
			   int, 	backwards, 
			   int, 	ignore_case)
{
    int		found = 0;
    SM_SCREEN	screen, current_screen;
    int		step = backwards? -1: 1;
    CHAR	*triage, *p, *record;

    assert(pattern && string && search_field);

    screen = search_field->parent;
    if (index < 0) /* Search from end of file */
	index = smdb_num_records(screen) - 1;

    triage = alloca(strlen(pattern) + 1);
    if (*pattern != '^')
	strcpy(triage, pattern);
    else
	strcpy(triage, pattern + 1);
    if (*SM_LASTCHAR(triage) == '$')
	*SM_LASTCHAR(triage) = 0;
    if (*triage) {
	if (re_comp(triage))
	    *triage = 0;
    } else
	re_comp(pattern);
   
    for (;!found && ((record = smdb_seek(screen, index)) != NULL);
	 index += step) {
	if (*triage) {
	    p = xmalloc((INDEX) (strlen(record) + 1));
	    strcpy(p, record);
	    if (ignore_case)
		sm_toupper(p);
	    if (!re_exec(p)) {
		free(p);
		continue;
	    } else
		re_comp(pattern);
	    free(p);
	}

	if (sm_user_screens()->tagname) {
	    /* Assumption here that if we search on a tagged file 
	       it is in sm_user_screens!! */
	    current_screen = sm_choose_scr(sm_user_screens(), record);
	    if (current_screen != screen)
		continue;
	}
	sm_data_to_screen(search_field->parent, record, 0);
	strcpy(string, sm_fld_enq(search_field));
	if (ignore_case)
	    sm_toupper(string);
	found = (re_exec(string)? 1: 0);
	if (found)
	    return(index);
	if (*triage)
	    re_comp(triage);
    }

    return(SM_ERROR);
}

/* On a scrolling screen, return the part of the record that does not
   scroll */
static void sm_static_part ARG3(CHAR *, 	static_part, 
				SM_SCREEN, 	screen, 
				CHAR *, 	data)
{
    CHAR	*sep = NULL, *term, *p;
    INDEX	data_desc = 0;
    int		in_static_part = 0; /* 000001111111-1-1-1-1 */
				    /*	    ^^^^^^^ == static part */
    SM_FIELD	*fieldp, field;

    if ((data == NULL) || (screen == NULL)) {
	*static_part = 0;
	return;
    }

    if (!(screen->scrolling))
	strcpy(static_part, data);
    else {
	term = strchr(data, 0);
	fieldp = sm_first_fld(screen);
	*static_part = 0;
	while (fieldp && *fieldp && 
	       (data < term) &&
	       (in_static_part >= 0)) {
	    
	    /* Decide which fields to use */
	    if (screen->data_field && 
		(p = (CHAR *) arrayItem(screen->data_field, data_desc))) {
		
		/* Use screen data descriptions to assign data to
		   appropriate fields */
		field = sm_fld_open(screen, p);
	    } else {
		/* No screen data descriptions - fill fields 
		   sequentially */
		field = *fieldp;
	    }

	    /* are we in the static part? */
	    if ((in_static_part == 0) &&
		screen->constant_start &&
		(strcmp(screen->constant_start, field->field_name) == 0))
		in_static_part = 1;
	    if ((in_static_part == 1) &&
		screen->constant_end &&
		(strcmp(screen->constant_end, field->field_name) == 0))
		in_static_part = -1;

	    sep = sm_parse(data, 
			   (field && (field->len < 0))? abs(field->len): 0,
			   screen->parent->separator);
	    
	    /* Copy it in if it is part of the static data */
	    if (in_static_part == 1) {
		if (field && (field->len < 0)) {
		    strncpy(static_part, data, abs(field->len));
		    static_part[abs(field->len)] = 0;
		} else
		    strcpy(static_part, data);
		static_part = strchr(static_part, 0);
	    }
		
	    /* Next piece of data */
	    data = sm_nextfield(screen->parent->separator,
				data,
				sep, 
				(!field || (field->len >= 0)? 
				 0: abs(field->len)));
		
	    if (sep && (sep < term))
		*sep = screen->parent->separator;
		
	    if (field && (screen->data_field == NULL))
		fieldp = sm_next_fld(fieldp);
	    else
		field = NULL;

	    data_desc++;
	}
	
	if (sep && (sep < term))
	    *sep = screen->parent->separator;
    }
}

static void _sm_insert_data ARG1(SM_SCREEN, this_screen)
{
    CHAR	*buf = alloca(SM_MAXREC), *p;
    SM_FIELD	*fieldp, field;

    assert(this_screen);

    if ((p = smdb_get_current(this_screen)))
	strcpy(buf, p);
    else
	*buf = 0;

    /* On scrolling files:
       copy the existing record - including any tag, if present
       increase the index, 
       initialise the non-static parts */
    if (this_screen->scrolling) {
	sm_data_to_screen(this_screen, buf, 0);
	for (fieldp = sm_first_fld(this_screen); 
	     fieldp && *fieldp;
	     fieldp = sm_next_fld(fieldp)) {
	    field = *fieldp;
	    if (field->row >= this_screen->scrolling - 1)
		sm_fld_init(field);
	}
	sm_screen_to_data(buf, this_screen);
	smdb_read_next(this_screen);
    } else if (sm_user_screens()->tagname) {
	/* On tagged files, copy the existing record - including tag!*/
	/* strcpy(buf, this_screen->data[this_screen->index + 1]);
	   ... done above */
    } else {
	sm_scr_init(this_screen, /* recursive = */ 1);
	sm_screen_to_data(buf, this_screen);
    }
    
    smdb_insert_rec(this_screen, buf);
    sm_message("New data record inserted");
}

/* Recursively delete a record (and any detail records that point to
   it, if it is a master record) */
static CHAR *_sm_delete_data ARG6(SM_SCREEN, 	this_screen, 
				  CHAR *, 	yank, 
				  INDEX,	first_data_on_screen, 
				  INDEX,	last_data_on_screen, 
				  int, 		prompt, 
				  SM_SCREEN, 	master_screen)
{
    CHAR	*record;

    if (smdb_get_current(this_screen)) {
	if (sm_editing_master()) {
	    int		detail_records_present = FALSE;
	    SM_SCREEN	d_screen;
	    SM_FIELD	*fp, d_field, temp;

/* 1. Find detail screen if any (may be several layers down) */

/* Fudge so that sm_next_fld_on_screen will scan all the fields on */
/* the screen */
	    temp = sm_detail_field();
	    sm_edit_master(NULL); 
	    d_screen = NULL;
	    for (fp = sm_first_fld_on_screen(master_screen);
		 fp && *fp;
		 fp = sm_next_fld_on_screen(master_screen, fp))
		if ((*fp)->master_screen && 
		    ((*fp)->master_screen == this_screen)) {
		    d_screen = (*fp)->parent;
		    d_field = *fp;
		    break;
		}
	    sm_edit_master(temp); /* fudge finished */

/* 2. find any detail records with this master value */
	    if (d_screen) {
		if (prompt) {
		    record = smdb_seek(d_screen, (INDEX) 0);
		    do {
			sm_data_to_screen(d_screen, record, 0);
			if (strcmp(d_field->c_value,
				   d_field->master_fieldp->c_value) == 0) {
			    detail_records_present = TRUE;
			    break;
			}
		    } while ((record = smdb_read_next(d_screen)));
		    if (detail_records_present)
			if (sm_confirm("Delete all detail records with this key?")
			    != SM_YES_C)
			    detail_records_present = FALSE; /* lie and cheat! */
		} else
		    detail_records_present = TRUE;

/* 3. Now delete them */
		if (detail_records_present) {
		    record = smdb_seek(d_screen, (INDEX) 0);
		    while (record) {
			sm_data_to_screen(d_screen, record, 0);
			if (strcmp(d_field->c_value, 
				   d_field->master_fieldp->c_value) == 0) {
			    _sm_delete_data(d_screen, 
					    NULL, 
					    (INDEX) 0, 
					    (INDEX) 0, 
					    0,
					    master_screen);
			    /* _sm_delete_data moves the next record 
			       into place */
			    record = smdb_get_current(d_screen);
			} else
			    record = smdb_read_next(d_screen);
		    }
		}
	    }
	}

	strcpy(yank, smdb_get_current(this_screen));
	if (smdb_delete_rec(this_screen))
	    sm_error("RECORD WAS NOT DELETED!");
	else
	    sm_message("One record was deleted ");
	
	if (this_screen->scrolling) {
	    /* Go to previous record, rather than next */
	    INDEX	pos = smdb_enq_position(this_screen);
	    if (pos &&
		(pos > first_data_on_screen) &&
		(pos == last_data_on_screen))
		smdb_seek(this_screen, pos - 1);
	    /* Force redisplay */
	    last_data_on_screen = MAX_INDEX;
	}
    } else
	sm_message("Nothing to delete!");
    return(yank);
}

static int _sm_yank_data ARG2(SM_SCREEN, this_screen, CHAR *, yank)
{
    assert(this_screen);

    if (smdb_get_current(this_screen)) {
	if (sm_confirm("Overwrite current record?") != SM_YES_C)
	    return(0);
    }
    
    /* Don't reset any record timestamp */
    smdb_set_current(this_screen, yank);
    return(1);
}

static void _sm_copy_data ARG2(SM_SCREEN, this_screen, CHAR *, yank)
{
    assert(this_screen);

    if (smdb_get_current(this_screen)) {
	strcpy(yank, smdb_get_current(this_screen));
    } else
	sm_message("Nothing to copy!");
}

static int move_within_static ARG3(SM_SCREEN, 		this_screen, 
				   struct edit_s *,	edit,
				   int,			forward)
{
    /* move up to one screenfull within the same static part */
    CHAR	*static_part = alloca(SM_MAXREC);
    int		maxrow, maxcol, numrow, moves = 0;
    f_ptr	move, unmove;

    assert(this_screen);

    if (forward) {
	move = (f_ptr) smdb_read_next;
	unmove = (f_ptr) smdb_read_prev;
    } else {
	move = (f_ptr) smdb_read_prev;
	unmove = (f_ptr) smdb_read_next;
    }

    sm_enq_term(edit->window, &maxrow, &maxcol);
    numrow = maxrow - this_screen->scrolling;
    while (numrow-- && smdb_get_current(this_screen)) {
	if ((CHAR *)((*move)(this_screen)) == NULL) {
	    break;
	}
	if (edit->static_part[0]) {
	    sm_static_part(static_part, 
			   this_screen, 
			   smdb_get_current(this_screen));
	    
	    if (strcmp(edit->static_part, static_part)) {
		(*unmove)(this_screen);
		break;
	    }
	    moves++;
	}
    }
    return(moves);
}

static void _sm_next_screen ARG2(SM_SCREEN, 		this_screen, 
				 struct edit_s *,	edit)
{
    assert(this_screen && edit);

    if (smdb_get_current(this_screen)) {
	if (this_screen->scrolling) {
	    if (smdb_enq_position(this_screen) == edit->last_data_on_screen) {
		if (move_within_static(this_screen, edit, 1) == 0)
		    smdb_read_next(this_screen);
	    } else
		smdb_seek(this_screen, edit->last_data_on_screen);
	} else
	    smdb_read_next(this_screen);
    } else
	sm_message("No next record!");
}

static void _sm_prev_screen ARG2(SM_SCREEN, 		this_screen, 
				 struct edit_s *,	edit)
{
    INDEX	pos;

    assert(this_screen && edit);

    if ((pos = smdb_enq_position(this_screen))) {
	if (this_screen->scrolling) {
	    if (pos == edit->first_data_on_screen) {
		if (move_within_static(this_screen, edit, 0) == 0)
		    smdb_read_prev(this_screen);
	    } else
		smdb_seek(this_screen, edit->first_data_on_screen);
	} else
	    smdb_read_prev(this_screen);
    } else
	sm_message("No previous record!");
}

static void _sm_clear_screen ARG2(SM_SCREEN, this_screen, CHAR *, yank)
{
    assert(this_screen);

    if (smdb_get_current(this_screen)) {
	strcpy(yank, smdb_get_current(this_screen));
	smdb_set_current(this_screen, "");
    }
}

static int _sm_open_master_screens ARG2(SM_SCREEN, 	this_screen, 
					int *, 		base_screen_has_masters)
{
    SM_FIELD	*fieldp, field;
    CHAR	buf[80];

    assert(this_screen);

    for (fieldp = sm_first_fld(this_screen); 
	 fieldp && *fieldp;
	 fieldp = sm_next_fld(fieldp)) {

	field = *fieldp;
	if (field->master_screen_name && *field->master_screen_name) {
	    *base_screen_has_masters = TRUE;
	    if ((field->master_screen 
		 = sm_scr_open(sm_user_screens(),
			       field->master_screen_name)) == NULL) {
		sprintf(buf,"SM_EDIT: no such screen <%s>", field->master_screen_name);
		sm_message(buf);
		return(SM_ERROR);
	    }
	    if ((field->master_fieldp = sm_fld_open(field->master_screen, 
						 field->field_name)) == NULL) {
		sprintf(buf, "SM_EDIT: no such field <%s> in screen <%s>",
			field->field_name, field->master_screen_name);
		sm_message(buf);
		return(SM_ERROR);
	    }
	    _sm_open_master_screens(field->master_screen, base_screen_has_masters);
	}
    }
    return(0);
}

/* Rules to find the detail field:
   	1. if I point at a screen then I am the detail field.
   	2. if a field points at me, then it is the detail field.
   	3. if no field points at me then any field that points at
   	   my screen is the detail. */
static SM_FIELD is_a_detail_field ARG2(SM_SCREEN, 	this_screen,
				       SM_FIELD,	field)
{
    SM_FIELD	d_field, *fp;

    if (field == NULL)
	return(NULL);

    d_field = field;
    
    if (!(d_field->master_fieldp)) { /* Rule 1 */

	/* field is not a detail field.
	   Find the detail field of interest ...I assume that
	   there is always exactly one ... !? */

	for (fp = sm_first_fld_on_screen(this_screen);
	     fp && *fp;
	     fp = sm_next_fld_on_screen(this_screen, fp)) {
	    if ((*fp)->master_fieldp == field)
		break; /* Rule 2 */
	    else if ((*fp)->master_screen == field->parent)
		break; /* Rule 3 */
	}
	
	if (fp && *fp)
	    d_field = *fp;
	else
	    d_field = NULL;
    }
    return(d_field);
}

/* This assumes that d_field is a detail field */
static void update_detail_key ARG1(SM_FIELD,		d_field)
{
    SM_SCREEN	d_screen;
    int		do_all;
    INDEX	old_index;
    CHAR	*old_master_value = NULL;
    CHAR	*buf = alloca(SM_MAXREC), *record;

    assert(d_field);

    d_screen = d_field->parent;
    old_index = smdb_enq_position(d_screen);

    sprintf(buf, 
	    "Change ALL \"%.15s...\" to \"%.15s...\" in detail file?", 
	    d_field->c_value, 
	    d_field->master_fieldp->c_value);
    
    if (strlen(d_field->c_value) &&
	(sm_confirm(buf) == SM_YES_C)) {
	do_all = TRUE;
	smdb_seek(d_screen, (INDEX) 0);
    } else {
	do_all = FALSE;
	/* Create a new record if necessary ... */
	if (smdb_get_current(d_screen) == NULL)
	    smdb_set_current(d_screen, "");
    }
    
    SM_ASSIGN(old_master_value, d_field->c_value);
    
    sm_message("Updating records, please wait ... ");
    while ((record = smdb_get_current(d_screen))) {
	sm_data_to_screen(d_screen, record, 0);
	if (strcmp(d_field->c_value, 
		   old_master_value) == 0) {
	    SM_ASSIGN(d_field->c_value, d_field->master_fieldp->c_value);
	    sm_update_record_timestamp(d_screen);
	    sm_screen_to_data(buf, d_screen);
	    smdb_set_current(d_screen, buf);
	}
	if (!do_all)
	    break;
	smdb_read_next(d_screen);
    }
    smdb_seek(d_screen, old_index);
    sm_message("");
    free(old_master_value);
}

static void sm_record_num ARG1(SM_SCREEN, screen)
{
    CHAR	buf[MAXPATHLEN + 1];

    if (screen) {
	int i;

	if ((i = smdb_enq_position(screen)) >= 0)
	    sprintf(buf, "Record: %d", i + 1);
	else
	    strcpy(buf, "End");
	
	if (screen->filename) {
	    strcat(buf, " of ");
	    strcat(buf, screen->filename);
	}
	
	if (smdb_file_changed(screen))
	    strcat(buf, " (needs saving)");
	
	sm_message(buf);
    }
}

static INDEX edit_master ARG3(SM_SCREEN, 	this_screen,
			     INDEX,		old_index,
			     SM_FIELD *,	field)
{
    CHAR	*buf = alloca(80);

    if (sm_editing_master()) {
	SM_FIELD	detail_field = sm_detail_field();
	SM_SCREEN	detail_screen = detail_field->parent;
	SM_SCREEN	master_screen = sm_editing_master();
	
	/* Unlock screen now, so that first_fld_on_screen() works! */
	sm_edit_master(NULL);
	
	/* This is tricky, let me explain. We only want to
	   change the detail record if the master record is 
	   a different one or has had its key changed */
	
	sprintf(buf, "Lock changes into detail file \"%.20s\"?",
		detail_screen->filename);
	if (((sm_scr_changed(master_screen) && 
	      sm_fld_changed(detail_field->master_fieldp)) ||
	     (smdb_enq_position(master_screen) != old_index)) &&
	    sm_confirm(buf) == SM_YES_C) {
	    update_detail_key(detail_field);
	}
	sm_record_num(this_screen);
	
    } else { /* ! sm_editing_master */
	
	SM_FIELD	detail_field = NULL;
	
	/* Start editing a master file. */

	detail_field = is_a_detail_field(this_screen, *field);
	if (detail_field == NULL) {
	    sm_message("Not a detail field!");
	    return(old_index);
	}

	/* detail_field is a detail field */
	if (detail_field && detail_field->master_fieldp) {
	    sm_edit_master(detail_field);
	    old_index = smdb_enq_position(sm_editing_master());
	    if (*field == detail_field)
		*field = detail_field->master_fieldp;
	    sm_record_num(sm_editing_master());
	}
    }
    return(old_index);
}

struct master_s {
    f_ptr	destructor;
    SM_SCREEN	m_screen, d_screen;
    SM_FIELD	d_field;
    INDEX	index;
    INDEX	old_index;
    SM_SOFTKEY	skey_store;
};

int master_destructor ARG1(struct master_s *, object)
{
    free(object);
    return(0);
}
    
CALLBACK(do_master)
{
    struct master_s 	*master = (struct master_s *) callback_data;
    CHAR		*buf = alloca(SM_MAXREC);

    sm_paint_skey(master->skey_store);

    /* Always called after sm_selection */
    if (command != SM_QUIT_C) {
	if (master->old_index != smdb_enq_position(master->m_screen)) {
 	    sprintf(buf, "Lock new key value into detail file \"%.20s\"?",
		    master->d_screen->filename);
	    if (sm_confirm(buf) == SM_YES_C) {
		if (!smdb_get_current(master->d_screen))
		    smdb_set_current(master->d_screen, "");
		sm_data_to_screen(master->m_screen,
				  smdb_seek(master->m_screen, master->index),
				  0);
		update_detail_key(master->d_field);
	    }
	}
	if (command == SM_YES_C)
	    command = SM_POPCALLBACK;
    } else
	command = SM_POPCALLBACK;
	
    return(command);
}

static int popup_master ARG2(SM_SCREEN, this_screen, SM_FIELD, field)
{
    struct master_s *master;

    master = (struct master_s *)xmalloc((INDEX) sizeof(struct master_s));
    master->destructor = master_destructor;
    
    master->d_field = is_a_detail_field(this_screen, field);
    
    /* We now know that master->d_field is the detail field */
    if (master->d_field && (master->d_field->master_fieldp)) {
	master->m_screen = master->d_field->master_fieldp->parent;
	master->d_screen = master->d_field->parent;
	master->index = smdb_enq_position(master->m_screen);
	master->old_index = master->index;
	
	/* Eventually it would be better to create a set
	   of key values only - not show the user the entire 
	   record! */
	master->skey_store = smio_current_skey;
	sm_add_callback(do_master, master);
	sm_setup_selection(master->m_screen, 
			   &master->index,
			   0, 
			   0, 
			   /* helper = */ NULL,
			   /* helperdata = */ NULL,
			   sm_prompt_skey);
	return(0);
    } else {
	sm_message("Not a key field!");
	return(SM_ERROR);
    }
}

/* See smlib.h for this ... shared with smwhere
struct edit_s {
*/

int edit_destructor ARG1(struct edit_s *, edit)
{
    sm_where_close_file(edit);
    free(edit);
    return(0);
}

struct select_scr_s {
    f_ptr		destructor;
    struct edit_s	*edit;
    SM_SCREEN		list_scr;
    INDEX		index;
    SM_SCREEN		newscreen;
};

int select_scr_destructor ARG1(struct select_scr_s *, select_scr)
{
    free(select_scr);
    return(0);
}

/* For tagged files */
CALLBACK(do_select_scr)
{
    struct select_scr_s *select_scr = (struct select_scr_s *) callback_data;
    SM_SCREEN		this_screen = select_scr->edit->base_screen;
    CHAR		*buf = alloca(SM_MAXREC);

    select_scr->newscreen = 
	sm_scr_open(sm_user_screens(),
		    smdb_get_current(select_scr->list_scr));

    /* Always called after sm_selection */
    if ((command == SM_YES_C) &&
	select_scr->newscreen && 
	(select_scr->newscreen != select_scr->edit->current_screen)) {

	select_scr->edit->last_tagged_screen = NULL;
	select_scr->edit->current_screen = select_scr->newscreen;
	
	if (smdb_get_current(this_screen) == NULL)
	    /* It might be good to copy the last record of this 
	       type - save typing? */
	    sm_scr_init(select_scr->edit->current_screen, /* recursive = */ 1);
	else {
	    sm_data_to_screen(select_scr->edit->current_screen, 
			      smdb_get_current(this_screen), 
			      0);
	    sm_fld_set(sm_fld_open(select_scr->edit->current_screen,
				   sm_user_screens()->tagname),
		       smdb_get_current(select_scr->list_scr));
	}

	sm_update_record_timestamp(select_scr->edit->current_screen);
	sm_screen_to_data(buf, select_scr->edit->current_screen);
	smdb_set_current(this_screen, buf);
	command = SM_POPCALLBACK;
    } else if (command == SM_QUIT_C)
	command = SM_POPCALLBACK;

    smdb_delete_data(select_scr->list_scr);
    return(command);
}

/* On a tagged file, prompt the user to give a screen type */
static void sm_select_scr ARG1(struct edit_s *, edit)
{
    SM_SCREEN		*screenp;
    struct select_scr_s *select_scr;

    select_scr = (struct select_scr_s *)xmalloc((INDEX) sizeof(struct select_scr_s));
    select_scr->destructor = select_scr_destructor;
    select_scr->edit = edit;
    select_scr->list_scr = sm_prompt_scr; /* Hack!! */
    select_scr->newscreen = NULL;

    /* build a list of screen types */
    smdb_delete_data(select_scr->list_scr);
    for (screenp = sm_first_scr(sm_user_screens()); 
	 screenp && *screenp; 
	 screenp = sm_next_scr(screenp))
	smdb_write(select_scr->list_scr, (*screenp)->screen_name);

    /* Now find out which screen he wants */
    smdb_seek(select_scr->list_scr, (INDEX) 0);
    sm_add_callback(do_select_scr, select_scr);
    sm_setup_selection(select_scr->list_scr,
		       &select_scr->index,
		       0, 
		       0, 
		       /* helper =     */ NULL,
		       /* helperdata = */ NULL,
		       NULL);
}

static void sm_prep_scrolling_screen ARG1(struct edit_s *, edit)
{
    CHAR	*buf = alloca(SM_MAXREC), *rec;
    INDEX	this_rec, current_rec_pos, current_row;
    int		maxrow, maxcol;
    SM_FIELD	*fieldp, field;
    
    /* Get the display set up for the next I/O */
    this_rec = smdb_enq_position(edit->base_screen);

    /* It's annoying in a scrolling screen with a static part to fall off the
       end of a file onto the phantom record */
    if (edit->static_part[0] && this_rec) {
	if (this_rec > smdb_num_records(edit->base_screen) - 1) {
	    smdb_read_prev(edit->base_screen);
	    this_rec = smdb_enq_position(edit->base_screen);
	}
    }
    
    sm_enq_term(edit->window, &maxrow, &maxcol);
    
    sm_static_part(buf, 
		   edit->current_screen, 
		   smdb_get_current(edit->base_screen));
    
#if 0 
    /* You've got to repaint the whole form incase there's been a deletion
       or insertion - hence this kind of optimisation doesn't work */
    if ((edit->current_screen != edit->last_tagged_screen) || 
	strcmp(buf, edit->static_part) ||
	(smdb_enq_position(edit->base_screen) < 
	 edit->first_data_on_screen) ||
	(smdb_enq_position(edit->base_screen) > 
	 edit->last_data_on_screen)) {
    }
#endif	    

    strcpy(edit->static_part, buf);
    
    current_rec_pos = smdb_enq_position(edit->base_screen);
    current_row = current_rec_pos - edit->first_data_on_screen;

    /* Decide if SM_UP_C should have any effect */
    edit->can_go_up = 0;
    rec = smdb_read_prev(edit->base_screen);
    if (rec) {
	sm_static_part(buf, edit->current_screen, rec);
	if (strcmp(buf, edit->static_part) == 0)
	    edit->can_go_up = 1;
	smdb_read_next(edit->base_screen);
    }

    /* Read back till we can't go any further (start of file or till
       static part changes if any) */
    while ((rec = smdb_read_prev(edit->base_screen)) &&
	   (current_row-- > 0)) {
	/* Deal with tagged files - assume they are in USER_SCREENS!!*/
	if (sm_user_screens()->tagname) {
	    if (edit->current_screen == sm_dumb_scr) {
		if (sm_choose_scr(sm_user_screens(), rec) != NULL)
		    break;
	    } else/* Not a dumb_scr */
		if (sm_choose_scr(sm_user_screens(), 
				  rec) != edit->current_screen)
		    break;
	}
        
	/* Deal with scrolling screens */
	sm_static_part(buf, edit->current_screen, rec);
	if (strcmp(buf, edit->static_part))
	    break;
	
	/* Now, the limit is the number of lines on the screen */
	if (current_rec_pos - smdb_enq_position(edit->base_screen) >
	    /*(maxrow - (edit->current_screen->scrolling + 1)) / 2 )*/
	    (maxrow - edit->current_screen->scrolling))
	    break;
    }

    if (rec)
	edit->first_data_on_screen = smdb_enq_position(edit->base_screen) + 1;
    else
	edit->first_data_on_screen = 0;
    
    /* Now look forwards, painting as we go */
    smdb_seek(edit->base_screen, edit->first_data_on_screen);
    edit->current_screen->current_line = 
	edit->current_screen->scrolling - 1;
    while (smdb_get_current(edit->base_screen) && 
	   (edit->current_screen->current_line < maxrow)) {
	if (sm_user_screens()->tagname) {
	    /* Assume screens are in USER_SCREENS !! */
	    if (edit->current_screen == sm_dumb_scr) {
		if (sm_choose_scr(sm_user_screens(), 
				  smdb_get_current(edit->base_screen))
		    != NULL)
		    break;
	    } else
		if (sm_choose_scr(sm_user_screens(), 
				  smdb_get_current(edit->base_screen))
		    != edit->current_screen)
		    break;
	}
        
	sm_static_part(buf, edit->current_screen, 
		       smdb_get_current(edit->base_screen));
	if (strcmp(buf, edit->static_part))
	    break;
	
	/* Put data into screen */
	if (edit->current_screen == sm_dumb_scr) {
	    SM_ASSIGN(sm_dumb_fld->c_value, smdb_get_current(edit->base_screen));
	} else
	    sm_data_to_screen(edit->current_screen, 
			      smdb_get_current(edit->base_screen), 0);
	
	sm_paint_scr(edit->window, 
		     edit->current_screen, 
		     /* enhance = */ 1);
	(edit->current_screen->current_line)++;
	smdb_read_next(edit->base_screen);
    }
    
    edit->last_data_on_screen = 
	smdb_enq_position(edit->base_screen) - 1;
    
    /* Fill to the bottom of page */
    sm_data_to_screen(edit->current_screen, edit->static_part, 0);
    while (edit->current_screen->current_line < maxrow) {
	sm_paint_scr(edit->window, 
		     edit->current_screen, 
		     /* enhance = */ 1);
	(edit->current_screen->current_line)++;
    }
    
    /*smio_goto(edit->window, edit->current_screen->current_line, 0);
      smio_clear(edit->window, 1);*/
#if 0
    {}	    
#endif
    edit->current_screen->current_line = 
	edit->current_screen->scrolling - 1 + this_rec - 
	    edit->first_data_on_screen;
    for (fieldp = sm_first_fld(edit->current_screen); 
	 fieldp && *fieldp; 
	 fieldp = sm_next_fld(fieldp)) {
	field = *fieldp;
	if (field->row >= edit->current_screen->scrolling - 1)
	    field->row = edit->current_screen->current_line;
    }
    smdb_seek(edit->base_screen, this_rec);
}

static void sm_prep_tagged_screen ARG1(struct edit_s *, edit)
{
    /* Choose a screen to use if tagged data (otherwise use what we were 
       given). We assume that screens are in SM_USER_SCREENS!! */

    /* If this is a blank record at the end of the file, choose
       the same record type as the last record */
    if (smdb_get_current(edit->base_screen) == NULL) {
	if (smdb_enq_position(edit->base_screen))
	    if ((edit->current_screen = 
		 sm_choose_scr(sm_user_screens(), 
			       smdb_read_prev(edit->base_screen))
		 ) != NULL) {
		sm_data_to_screen(edit->current_screen, 
				  smdb_get_current(edit->base_screen), 
				  0);
		smdb_read_next(edit->base_screen);
	    }
    } else
	edit->current_screen = sm_choose_scr(sm_user_screens(), 
					     smdb_get_current(edit->base_screen));
    if (edit->current_screen == NULL)
	edit->current_screen = sm_dumb_scr;
}

CALLBACK(sm_edit_get_ready)
{
    struct edit_s 	*edit = (struct edit_s *) callback_data;
    SM_FIELD		*fieldp, field;

    if ((command == SM_EXIT_C) ||
	(command == SM_NO_SAVE_EXIT_C) ||
	(command == SM_CLEAR_FILE_C) ||
	(command == SM_STORE_FILE_AS_C) ||
	(command == SM_STORE_FILE_C) ||
	(command == SM_LOAD_FILE_C))
	return(command);

    /***********************/
    /* DEFAULT MESSAGE ... */
    /***********************/
    
    if (*sm_msg == 0) {
	if (sm_editing_master())
	    sm_record_num(sm_editing_master());
	else
	    sm_record_num(edit->base_screen);
    }
    
    /* Handle tagged screens */
    if (sm_user_screens()->tagname) 
	sm_prep_tagged_screen(edit);
    
    /* Handle scrolling screens */
    if (edit->current_screen->scrolling) 
	sm_prep_scrolling_screen(edit);

    /*********************************/
    /* COPY CURRENT RECORD TO SCREEN */
    /*********************************/
    
    edit->temp_sep = edit->current_screen->parent->separator;
    if (sm_editing_master()) {
	SM_SCREEN	this_screen = sm_editing_master();
	
	if (smdb_get_current(this_screen))
	    sm_data_to_screen(this_screen, 
			      smdb_get_current(this_screen), 0);
	else
	    sm_scr_init(this_screen, /* recursive = */ 1);
	
    } else if (smdb_get_current(edit->base_screen)) {
	if (edit->current_screen == sm_dumb_scr) {
	    SM_ASSIGN(sm_dumb_fld->c_value, smdb_get_current(edit->base_screen));
	    sm_dumb_scr->parent->separator = 0; /* Allow user to input ~ */
	    
	} else {
	    sm_data_to_screen(edit->current_screen, 
			      smdb_get_current(edit->base_screen), 0);
	    
	    if (edit->base_screen_has_masters) {
		/* Position master files for current detail file */
		for (fieldp = sm_first_fld_on_screen(edit->base_screen);
		     fieldp && *fieldp;
		     fieldp = sm_next_fld_on_screen(edit->base_screen, 
						    fieldp)) {
		    
		    field = *fieldp;
		    if (field->master_screen) {
			SM_FIELD 	master_field  = field->master_fieldp;
			SM_SCREEN	master_screen = field->master_screen;
			
			if (strlen(field->c_value) == 0)
			    smdb_seek(master_screen, MAX_INDEX);
			else if (strcmp(field->c_value, 
					master_field->c_value)) {
			    /* find it or put index at end */
			    smdb_seek(master_screen, (INDEX) 0);
			    while (smdb_get_current(master_screen)) {
				sm_data_to_screen(master_screen, 
						  smdb_get_current(master_screen),
						  0);
				if (strcmp(field->c_value, 
					   master_field->c_value) == 0)
				    break;
				smdb_read_next(master_screen);
			    }
			}
			sm_data_to_screen(master_screen, 
					  smdb_get_current(master_screen),
					  0);
		    }
		}
	    }
	}
    } else {
	/* Blank record */
	sm_scr_init(edit->current_screen, /* recursive = */ 1);
	
	/* Point any master screens at a blank record too */
	if (edit->base_screen_has_masters) {
	    SM_FIELD	*fdp;
	    SM_SCREEN	s;
	    
	    for (fdp = sm_first_fld_on_screen(edit->current_screen); 
		 fdp && *fdp;
		 fdp = sm_next_fld_on_screen(edit->current_screen, fdp))
		if ((s = (*fdp)->master_screen) != NULL) {
		    if (sm_non_blank(*fdp))
			/* Put at the first value */
			smdb_seek(s, (INDEX) 0);
		    else
			/* position at EOF */
			smdb_seek(s, smdb_num_records(s) - 1);
		}
	}
    }
    
    /* Force cursor to home if a blank screen */
    {
	SM_SCREEN	r_screen = edit->base_screen,
			c_screen = edit->current_screen; 
	SM_FIELD	*fieldp;
	
	if (sm_editing_master()) {
	    r_screen = sm_editing_master();
	    c_screen = r_screen;
	}
	if (!smdb_get_current(r_screen)) {
	    fieldp = sm_first_fld_on_screen(c_screen);
	    if (fieldp)
		edit->current_field = *fieldp;
	    else
		edit->current_field = NULL;
	}
    }
    /* Remove self from callback stack, if there. Command is only set 
       to SM_NOT_A_CALLBACK if called directly by sm_setup_edit or 
       sm_do_edit */
    if (command != SM_NOT_A_CALLBACK)
	sm_destroy_callback();
    sm_setup_scr(edit->window, 
		 edit->current_screen, 
		 edit->current_field, 
		 edit->target_for_field_popups, 
		 1, 
		 edit->target_col,
		 edit->can_go_up);
    return(0);
}

CALLBACK(goto_record)
{
    struct edit_s 	*edit = (struct edit_s *) callback_data;
    CHAR  		*s;
    INDEX		temp;

    /* Always called after sm_prompt */
    if (command == SM_YES_C) {
    	s = sm_get_prompt_value();
	if (*s) {
	    temp = AsciiToIndex(s) - 1; /* data[0] is record 1 to the user */
	    if (temp < (INDEX) 0)
		temp = 0;
	    
	    smdb_seek(edit->base_screen, temp);
        }
    }
    return(command);
}

static void reposition_detail ARG3(SM_SCREEN, 	detail_screen,
				   SM_SCREEN, 	master_screen,
				   CHAR *,	buf)
{
    SM_FIELD	*detail_fieldp;
    SM_FIELD	master_field;
    CHAR	*string = alloca(SM_MAXREC);
    INDEX	temp;
    
    /* Find the detail field ... */
    for (detail_fieldp = sm_first_fld(detail_screen);
	 detail_fieldp && *detail_fieldp;
	 detail_fieldp = sm_next_fld(detail_fieldp))
	if ((*detail_fieldp)->master_screen == master_screen)
	    break;
    
    if (detail_fieldp && *detail_fieldp) {
	master_field = (*detail_fieldp)->master_fieldp;
	if (strcmp((*detail_fieldp)->c_value, 
		   master_field->c_value) != 0) {
	    strcpy(buf, master_field->c_value);
	    temp = 0;
	    temp = sm_findit(buf,
			     string,
			     temp,
			     *detail_fieldp,
			     FALSE,
			     FALSE);
	    if (temp >= (INDEX) 0)
		smdb_seek(detail_screen, temp);
	}
    }
}

static void subst ARG4(CHAR *, dest, 
		       CHAR *, orig, 
		       CHAR *, string, 
		       CHAR *, substitute)	
/* dest -	New line is put here */              
/* orig -	Original line */                     
/* string - 	bopat etc (exported by re_exec) point here - but may have  
   		been upper case'd - string is the line that was actually 
		fed to re_exec */
/* substitute -	regular expression RHS */            
{
    /* 'string' & 'orig' have 3 segments: LLLPPPPRRR Left, Pattern and Right */
    *dest = 0;

    /* Copy in text to left of found pattern (from original line) */
    while (*string && (string < bopat[0])) {
	*dest++ = *orig++;
	string++;
    }
    *dest = 0;

    /* Put in new pattern */
    if (*substitute)
	re_subs(substitute, dest);
    dest += strlen(dest);

    /* Skip over old pattern */
    while (*string && (string < eopat[0])) {
	string++;
	orig++;
    }

    /* copy in the rest of the line */
    while ((*dest++ = *orig++))
	;
}

CALLBACK(do_replace)
{
    struct edit_s 	*edit = (struct edit_s *) callback_data;
    INDEX		temp;
    CHAR		*buf = alloca(SM_MAXREC);
    CHAR		*string = alloca(SM_MAXREC);
    CHAR		*dest = alloca(SM_MAXREC);
    SM_SCREEN		screen;

    /* Always called after sm_prompt */
    if ((command == SM_EXIT_C) || (command == SM_NO_SAVE_EXIT_C))
	return(command);

    if (command == SM_YES_C) {
	strcpy(global.substitute, sm_get_prompt_value());

	strcpy(buf, global.regular_exp);
	if (global.ignore_case)
	    sm_toupper(buf);
	
	/* start at the present position */
	screen = edit->current_field->parent;
	temp = smdb_enq_position(screen); 
	
	sm_message("Searching...");
	temp = sm_findit(buf, 
			 string, 
			 temp, 
			 edit->current_field, 
			 (command == SM_SEARCH_BACKWARDS_C), 
			 global.ignore_case);
	
	if (temp >= (INDEX) 0) {
	    /* bopat[] & eopat[] now point into 'string' which may have
	       been upper case'd */
	    sm_message("Found");
	    /* get the original back again */
	    sm_data_to_screen(screen, smdb_seek(screen, temp), 0);
	    strcpy(buf, sm_fld_enq(edit->current_field));
	    
	    subst(dest, buf, string, global.substitute);
	    sm_fld_set(edit->current_field, dest);
	    sm_update_record_timestamp(screen);
	    sm_screen_to_data(buf, screen);
	    smdb_set_current(screen, buf);
	} else
	    sm_message("Not found!");
	sm_data_to_screen(screen, smdb_get_current(screen), 0);
    }
    return(SM_POPCALLBACK);
}    

CALLBACK(search_for)
{
    struct edit_s 	*edit = (struct edit_s *) callback_data;
    CHAR		*buf = alloca(SM_MAXREC);
    CHAR		*cmpl;
    CHAR		*string = alloca(SM_MAXREC);
    INDEX		temp, here;
    SM_SCREEN		screen;

    /* Always called after sm_prompt */
    if ((command == SM_EXIT_C) || (command == SM_NO_SAVE_EXIT_C))
	return(command);

    if (command == SM_YES_C) {
	strcpy(global.regular_exp, sm_get_prompt_value());
	strcpy(buf, global.regular_exp);
	
	if (global.ignore_case)
	    sm_toupper(buf);
	
	/* Make sure that the regular expression is OK */
	cmpl = re_comp(buf);
	if (cmpl) {
	    sm_message(cmpl);
	    return(SM_ERROR);
	}
	
	if (edit->command == SM_SEARCH_REP_C) {	    
	    sm_add_callback(do_replace, NULL);
	    sm_setup_prompt("Replace with:", NULL, global.substitute, NULL);
	    return(0);
	} else if (edit->command == SM_SEARCH_C)
	    temp = 0; /* ... always start from the beginning of the file */
	else { /* edit->command == SM_SEARCH_BACKWARDS_C... start at the end */
	    temp = MAX_INDEX;
	}
	
	sm_message("Searching...");
	screen = edit->current_field->parent;
	here = smdb_enq_position(screen);
	temp = sm_findit(buf, 
			 string, 
			 temp, 
			 edit->current_field, 
			 (edit->command == SM_SEARCH_BACKWARDS_C), 
			 global.ignore_case);
	
	if (temp >= (INDEX) 0) {
	    /* bopat[] & eopat[] now point into 'string' which may have
	       been upper case'd */
	    sm_message("Found");
	    smdb_seek(screen, temp);
	    if ((screen != edit->current_screen) &&
		!sm_editing_master())
		/* Then we are in a master field but not editing it - we 
		   need to reposition the detail file */
		reposition_detail(edit->current_screen, screen, buf);
	} else {
	    sm_message("Not found!");
	    smdb_seek(screen, here);
	}
	sm_data_to_screen(screen, smdb_get_current(screen), 0);
    }
    return(SM_POPCALLBACK);
}

CALLBACK(sm_do_edit)
{
    struct edit_s 	*edit = (struct edit_s *) callback_data;
    int			i; 
    CHAR		*new_static_part, 
			*buf;
    SM_SCREEN		this_screen;
    int			inc;
    INDEX		current_line;
    INDEX		this_rec = smdb_enq_position(edit->base_screen);
    

    buf	            = alloca(SM_MAXREC);
    new_static_part = alloca(SM_MAXREC);

    edit->current_field = sm_get_field();
    smio_getyx(edit->window, &i, &edit->target_col);

    /* Might have been set for dumb screen ... */
    edit->current_screen->parent->separator = edit->temp_sep; 
    sm_message("");
    
    /***********************************/
    /* REGISTER ANY CHANGE IN THE DATA */
    /***********************************/
    
    {
	SM_SCREEN	r_screen = edit->base_screen,
			c_screen = edit->current_screen; 
	
	if (sm_editing_master())
	    r_screen = c_screen;
	
	if (sm_scr_changed(c_screen)) {
	    /* Create new record if at end-of-file */
	    if (smdb_get_current(r_screen) == NULL)
		smdb_set_current(r_screen, "");

	    if (c_screen == sm_dumb_scr) {
		smdb_set_current(r_screen, sm_dumb_fld->c_value);
	    } else {
		sm_update_record_timestamp(c_screen);
		sm_screen_to_data(buf, c_screen);
		smdb_set_current(r_screen, buf);
	    }
	}
    }
    
    /**********************************/
    /* POST-PROCESS SCROLLING SCREENS */
    /**********************************/
    
    if (sm_scr_changed(edit->current_screen) &&
	(edit->current_screen != sm_dumb_scr) && 
	edit->current_screen->scrolling) {
	
	/* See if the static part has been changed */
	sm_static_part(new_static_part, 
		       edit->current_screen, 
		       smdb_get_current(edit->base_screen));

	if (strcmp(new_static_part, edit->static_part))
	    /* It has - see if we need to change all the records with this
	       static part */
	    if ((smdb_num_records(edit->base_screen) > (INDEX) 1) &&
		(edit->base_screen->auto_adjust ||
		 (sm_confirm("Update all records?") == SM_YES_C))) {
		while (smdb_read_prev(edit->base_screen)) {
		    sm_static_part(buf, 
				   edit->current_screen, 
				   smdb_get_current(edit->base_screen));
		    if (strcmp(buf, edit->static_part) == 0) {
			INDEX	temp_rec =smdb_enq_position(edit->base_screen);

			sm_data_to_screen(edit->current_screen, 
					  smdb_get_current(edit->base_screen), 
					  0);
			/* go back to original record & pick up new static part */
			sm_data_to_screen(edit->current_screen, 
					  smdb_seek(edit->base_screen, this_rec), 1);
			
			/* Back again to the record to be altered */
			smdb_seek(edit->base_screen, temp_rec);
			sm_update_record_timestamp(edit->current_screen);
			sm_screen_to_data(buf, edit->current_screen);
			smdb_set_current(edit->base_screen, buf);
		    } else
			break;
		}
		smdb_seek(edit->base_screen, this_rec);
		while (smdb_read_next(edit->base_screen)) {
		    sm_static_part(buf, 
				   edit->current_screen, 
				   smdb_get_current(edit->base_screen));
		    if (strcmp(buf, edit->static_part) == 0) {
			INDEX	temp_rec = smdb_enq_position(edit->base_screen);

			sm_data_to_screen(edit->current_screen, 
					  smdb_get_current(edit->base_screen), 0);
			smdb_seek(edit->base_screen, this_rec);
			sm_data_to_screen(edit->current_screen, 
					  smdb_seek(edit->base_screen, this_rec), 1);
			sm_update_record_timestamp(edit->current_screen);
			sm_screen_to_data(buf, edit->current_screen);
			smdb_seek(edit->base_screen, temp_rec);
			smdb_set_current(edit->base_screen, buf);
		    } else
			break;
		}
		/* re-establish file position */
		smdb_seek(edit->base_screen, this_rec);
	    }
	strcpy(edit->static_part, new_static_part);
    }
    
    /********************************************************/
    /* ALLOW CALLING ROUTINE TO DO ITS STUFF IF IT WANTS TO */
    /********************************************************/
    
    if (edit->lambda && (command > 0)) {
	/* In case sm_prompt is called ... */
	sm_add_callback(sm_edit_get_ready, NULL);

	if (sm_editing_master())
	    command = (*edit->lambda) (command, sm_editing_master());
	else
	    command = (*edit->lambda) (command, edit->current_screen);

	if (command == SM_POPCALLBACK)
	    /* sm_prompt was called and we will need to run sm_edit_get_ready
	       again. */
	    return(0);
	else
	    /* no sm_prompt ... get rid of the sm_edit_get_ready callback */
	    sm_destroy_callback();
    }

    
    /********************/
    /* PROCESS COMMANDS */
    /********************/
    
    if (command < 0)
	command = edit->gedout;
    
    if ((command == edit->gedout) || 
	(command == SM_EXIT_C) || 
	(command == SM_NO_SAVE_EXIT_C) || 
	(command == SM_QUIT_C)) {
	if (sm_editing_master()) {
	    edit_master(edit->base_screen, 
			edit->old_index, 
			&edit->current_field);
	    edit->current_screen = edit->base_screen;
	}
	sm_message("");
	if (edit->old_skey)
	    sm_paint_skey(edit->old_skey);
	return(command);
    }

    switch (command) {
	
    case SM_INSERT_DATA_C:
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	_sm_insert_data(this_screen);
	edit->current_field = NULL;
	if (edit->current_screen->scrolling)
	    edit->last_data_on_screen = MAX_INDEX;
	break;
	
    case SM_DELETE_DATA_C:
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	_sm_delete_data(this_screen, 
			edit->yank, 
			edit->first_data_on_screen,
			edit->last_data_on_screen,
			1, /* prompt */ 
			edit->base_screen);
	if (edit->current_screen->scrolling)
	    edit->last_data_on_screen = MAX_INDEX;
	break;
	
    case SM_YANK_DATA_C:
	if (!*edit->yank) {
	    sm_message("Nothing to paste!");
	    break;
	}
	
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	if (_sm_yank_data(this_screen, edit->yank)) {
	    if (sm_user_screens()->tagname)
		edit->last_tagged_screen = NULL; /* In case the tag value changes */
	}
	break;
	
    case SM_COPY_DATA_C:
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	_sm_copy_data(this_screen, edit->yank);
	break;
	
    case SM_FIRST_SCREEN_C:
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	smdb_seek(this_screen, (INDEX) 0);
	break;
	
    case SM_LAST_SCREEN_C:
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	smdb_seek(this_screen, smdb_num_records(this_screen) - 1);
	break;
	
    case SM_UP_C:
	/* Only returned by a scrolling screen */
	if (edit->base_screen->scrolling)
	    if (!smdb_read_prev(edit->base_screen))
		sm_message("No previous record!");
	break;
	
    case SM_DOWN_C:
	/* Only returned by a scrolling screen */
	if (edit->base_screen->scrolling)
	    if (!smdb_read_next(edit->base_screen))
		sm_message("No next record!");
	break;

    case SM_MOUSE_EVENT_C:
	/* Only interesting here for a scrolling screen */
	if (edit->base_screen->scrolling) {
	    int		mouse_button, mouse_row, mouse_col, mouse_delta;
	    mouse_button = sm_enq_mouse(&mouse_row, &mouse_col, &mouse_delta);
	    if ((mouse_button & SM_MOUSE_LEFT) &&
		(mouse_button & SM_MOUSE_UP)) {

		inc = (mouse_row > edit->base_screen->current_line? 1:
		       (mouse_row < edit->base_screen->current_line? -1: 0));
		current_line = edit->base_screen->current_line - 
		    (edit->base_screen->scrolling - 1);
		mouse_row -= (edit->base_screen->scrolling - 1);
		
		if ((mouse_row >= 0) && 
		    (current_line != mouse_row))
		    do {
			current_line += inc;
			if (inc == 1)
			    smdb_read_next(edit->base_screen);
			else if (inc == -1)
			    smdb_read_prev(edit->base_screen);
		    } while (inc && smdb_enq_position(edit->base_screen) &&
			     smdb_get_current(edit->base_screen) &&
			     (current_line < edit->last_data_on_screen) &&
			     (current_line > edit->first_data_on_screen) &&
			     (current_line != mouse_row));
	    }
	}
	break;

    case SM_NEXT_LINE_C:
	/* Return at the end of a screen. */
	if (edit->base_screen->scrolling) {
	    if (smdb_get_current(edit->base_screen)) {
		smdb_read_next(edit->base_screen);
		if (edit->base_screen->constant_start)
		    edit->current_field = sm_fld_open(edit->base_screen,
						      edit->base_screen->constant_start);
		else
		    edit->current_field = *(sm_first_fld(edit->base_screen));
		edit->target_col = 0;
	    } else
		sm_message("No next record!");
	    break;
	} else
	    /* Otherwise, DROP THROUGH TO SM_NEXT_SCREEN_C HERE !!!! */
	    command = SM_NEXT_SCREEN_C;
    case SM_NEXT_SCREEN_C:
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	_sm_next_screen(this_screen, 
			edit);
	break;
	
    case SM_PREV_SCREEN_C:
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	_sm_prev_screen(this_screen, edit);
	break;
	
    case SM_CLEAR_SCREEN_C:
	this_screen = sm_editing_master();
	if (this_screen == NULL)
	    this_screen = edit->base_screen;
	_sm_clear_screen(this_screen, edit->yank);
	break;
	
    case SM_CHANGE_TYPE_C:
	/* tagged screens cannot edit master sets */
	if (sm_user_screens()->tagname) {
	    sm_select_scr(edit);
	    return(0);
	} else
	    sm_message("This is not a tagged file with variant record types.");
	break;
	
    case SM_EDIT_MASTER_C:
	if (edit->base_screen_has_masters && !(sm_user_screens()->tagname)) {
	    edit->old_index = edit_master(edit->base_screen, 
					  edit->old_index, 
					  &edit->current_field);
	    edit->current_screen = sm_editing_master();
	    if (edit->current_screen == NULL)
		edit->current_screen = edit->base_screen;
	}
	break;
	
    case SM_POPUP_C:
	if (edit->base_screen_has_masters && !(sm_user_screens()->tagname)) {
	    sm_add_callback(sm_edit_get_ready, NULL);
	    if (popup_master(edit->base_screen, edit->current_field) == 0)
		return(0);
	    else
		sm_destroy_callback();
	}
	break;
	
    case SM_GOTO_RECORD_C:
	sm_add_callback(sm_edit_get_ready, NULL);
	sm_add_callback(goto_record, NULL);
	sm_setup_prompt("Go to record #:", NULL, NULL, NULL);
	return(0);
	
    case SM_IGNORE_CASE_C:
	global.ignore_case = !(global.ignore_case);
	break;
	
    case SM_SEARCH_REP_C:
    case SM_SEARCH_C:
    case SM_SEARCH_BACKWARDS_C:
	edit->last_op = command;
	if (edit->current_field == NULL)
	    break;
	if ((command == SM_SEARCH_REP_C) && 
	    !sm_editing_master() &&
	    (edit->base_screen != edit->current_field->parent)) {
	    /* Then we're in a master field */
	    sm_message("You are not editing this master file yet!");
	    break;
	}

	edit->command = command;
	sm_add_callback(sm_edit_get_ready, NULL);
	sm_add_callback(search_for, NULL);
	sm_setup_prompt("Search for:", NULL, global.regular_exp, NULL);
	return(0);
	
    case SM_REPLACE_ALL_C:
	if (edit->last_op != SM_SEARCH_REP_C) {
	    sm_message("Do what?");
	    break;
	}
    case SM_AGAIN_C:
	if (edit->last_op && *global.regular_exp) {
	    CHAR	*cmpl, *string = alloca(SM_MAXREC);
	    int 	number_changed = 0, step;
	    INDEX	temp;
	    CHAR	*old_field 	= alloca(SM_MAXREC);
	    CHAR	*dest 		= alloca(SM_MAXREC);

	    if (edit->current_field == NULL)
		break;
	    
	    if (((edit->last_op == SM_SEARCH_REP_C) ||
		 (command == SM_REPLACE_ALL_C)) &&
		!sm_editing_master() &&
		(edit->base_screen != edit->current_field->parent)) {
		/* Then we're in a master field */
		sm_message("You are not editing this master file yet!");
		break;
	    }
	    
	    /* Assemble and check the regular expression */
	    strcpy(buf, global.regular_exp);
	    if (global.ignore_case)
		sm_toupper(buf);
	    if ((cmpl = re_comp(buf))) {
		sm_message(cmpl);
		break;
	    }
	    
	    if (edit->last_op == SM_SEARCH_BACKWARDS_C) {
		if (smdb_enq_position(edit->current_screen) == (INDEX) 0) { /* B */
		    sm_message("At start already!");
		    break;
		}
	    } else {
		if (smdb_enq_position(edit->current_screen) >=  /* B */
		    smdb_num_records(edit->current_screen)) { /* B */
		    sm_message("At end already!");
		    break;
		}
	    }
	    
	    if (edit->last_op == SM_SEARCH_BACKWARDS_C)
		step = -1;
	    else
		step = 1;

	    sm_message("Searching...");
	    
	    temp = smdb_enq_position(edit->current_screen) + step; /* B */
	    while (smdb_seek(edit->current_screen, temp)) { /* B */
		temp = sm_findit(buf, 
				 string, 
				 temp, 
				 edit->current_field, 
				 (edit->last_op == SM_SEARCH_BACKWARDS_C),
				 global.ignore_case);
		
		if (temp >= (INDEX) 0) {
		    smdb_seek(edit->current_screen, temp); /* B */

		    if ((command == SM_REPLACE_ALL_C) ||
			(edit->last_op == SM_SEARCH_REP_C)) {
			/* get the original back again */
			sm_data_to_screen(edit->current_screen,  /* B */
					  smdb_get_current(edit->current_screen), /* B */
					  0);
			strcpy(old_field, sm_fld_enq(edit->current_field));
			
			subst(dest, old_field, string, global.substitute);
			sm_fld_set(edit->current_field, dest);
			sm_update_record_timestamp(edit->current_screen);
			sm_screen_to_data(old_field, edit->current_screen); /* B */
			smdb_set_current(edit->current_screen, old_field); /* B */
			number_changed++;

		    } else if (edit->base_screen != 
			       edit->current_field->parent)
			/* Then we are in a master field but not editing it -
			   we need to reposition the detail file */
			reposition_detail(edit->current_field->parent, 
					  edit->base_screen, 
					  buf);
		    temp += step;
		}
		if (command != SM_REPLACE_ALL_C)
		    break;
	    }
	    
	    if (number_changed) {
		sprintf(buf, "%d records changed.", number_changed);
		sm_message(buf);
	    } else if (temp < (INDEX) 0)
		sm_message("Not found!");
	    else
		sm_message("Found");
	}
	break;

    case SM_WHERE_C:
	if (edit->current_field == NULL)
	    break;
	if (edit->base_screen != edit->current_field->parent) {
	    /* Then we're in a master field */
	    sm_message("You are not editing this master file yet!");
	    break;
	}

	edit->command = command;
	sm_add_callback(sm_edit_get_ready, NULL);
	sm_setup_where(edit);
	return(0);

    case SM_NEXT_KEYS_C:
	if (edit->set_in_use == sm_data_skey)
	    edit->set_in_use = sm_second_data_skey;
	else if (edit->set_in_use == sm_second_data_skey)
	    edit->set_in_use = sm_third_data_skey;
	else if (edit->set_in_use == sm_third_data_skey)
	    edit->set_in_use = sm_sort_skey;
	else if (edit->set_in_use == sm_sort_skey)
	    edit->set_in_use = sm_data_skey;
	sm_paint_skey(edit->set_in_use);
	break;

    case SM_SORT_C:
	{
	    struct sm_sortkey 	sortkey;
	    SM_SORTKEY		skp[2], *keys;
	    int	  		ans;
	    SM_SCREEN		screen;

	    skp[0] = NULL;
	    skp[1] = NULL;
	    if (edit->current_field == NULL)
		break;
	    
	    screen = edit->current_field->parent;

	    keys = sm_first_sortkey(screen);

	    if (screen->temp_storage != 'M') {
		sm_message("Can't SORT unless the file is in memory");
		break;
	    }

	    if (!keys || *keys == NULL) {
		keys = &skp[0];
		skp[0] = &sortkey;
		skp[1] = NULL;
		sortkey.sortfield = edit->current_field;
		sortkey.sortfield_name = edit->current_field->field_name;
		if ((ans = sm_confirm("Ascending order?")) == SM_CANCEL_C)
		    break;
		
		sortkey.reverse = ans == SM_YES_C? 0: 1;
		sortkey.ignore_case = global.ignore_case;
		sortkey.month = 0;
		if ((ans = sm_confirm("Numeric sort?")) == SM_CANCEL_C)
		    break;
		
		sortkey.numeric = ans == SM_YES_C? 1: 0;
	    }
		
	    if (smdb_sort_data(screen->data, keys) == 0) {
		smdb_set_file_changed(screen);
		smdb_seek(screen, (INDEX) 0);
	    }
	}
	break;

    case SM_REFRESH_C: /* probably from the lambda function */
	if (edit->current_screen->scrolling)
	    edit->last_data_on_screen = MAX_INDEX;
	break;

    case 0: /* Probably returned by the lambda */
	break;

    default:
	return(command); /* let some other sod sort it out! */
	break;
    }
    
    edit->last_tagged_screen = edit->current_screen;
    
    sm_edit_get_ready(SM_NOT_A_CALLBACK, edit);
    return(0);
}

/* Generic data editing program (gedout is a value to return)
 * 'data' may be subject to realloc and is therefore the return
 * value. The calling program is responsible for setting any
 * softkey values to the manifest constants used here, if needed,
 * although some may be set by sm_enter_scr/sm_enter_fld/smio_getch
 *
 * The function 'lambda' is called before any processing by sm_edit - it
 * has the opportunity to remap the operation through the return value.
 * For example, to remap SM_NEXT_SCREEN_C to SM_UP_C - simply return
 * SM_UP_C!
 *
 * int lambda(	int		command,
 *		SM_SCREEN	screen)
 *
 * If should also process any user defined functions on the data
 * (user defined softkeys have value >= 0600).
 *
 * If 'lambda' returns < 0 then sm_edit returns SM_OK immediately.
 *
 * 'lamba' should return 0 to prevent sm_edit from processing any further.
 * Also, if lambda calls sm_setup_prompt or any other popup
 * window for event processing then it must return SM_POPCALLBACK to allow
 * sm_edit to return(0) so that sm_edit_get_ready() is called again.
 * Mayhem results if this is not done!
 *
 * Since 'lambda' gets the base_screen as an argument it has the
 * opportunity to change the data, index and dirty_file.
 *
 * A non-NULL 'sm_user_screen()->tagname' indicates that the data
 * contains multiple data types, the screen name of which is defined in
 * the 'tag'th field.  sm_edit changes to that screen automatically. If
 * 'tag' is 0 then 'screen' should be set to the screen to be used for
 * all the data.
 *
 * This is the highest level routine in the suite, calling sm_enter_scr
 * which calls sm_enter_fld, which calls sm_getch, which calls
 * smio_getch. Arcane and gothic.
 */

/* Note: ignore case is always ON at startup but may be changed later
   If you use your own skey labels then make sure that any key for 
   ignore_case is ON
*/
int sm_setup_edit ARG6(int, 		window, 
		       SM_SCREEN, 	in_screen, 
		       int, 		gedout, 
		       f_ptr, 		lambda,
		       SM_SOFTKEY,	skey,
		       SM_SCREEN, 	target_for_field_popups)
{
    struct edit_s *edit;

    edit = (struct edit_s *)xmalloc((INDEX) sizeof(struct edit_s));
    edit->destructor = edit_destructor;
    
    edit->first_data_on_screen = MAX_INDEX;
    edit->last_data_on_screen = MAX_INDEX;
    edit->base_screen_has_masters = 0;
    edit->base_screen = NULL;
    edit->last_tagged_screen = NULL;	/* Only for tagged files */
    edit->yank = global.yank;
    edit->current_screen = 
	edit->base_screen = in_screen;
    edit->current_field = NULL;
    edit->lambda = lambda;
    edit->gedout = gedout;
    edit->window = window;
    edit->old_index = 0;
    edit->old_skey = smio_current_skey;
    edit->f = NULL;
    edit->target_col = 0;
    edit->target_for_field_popups = target_for_field_popups;

    sm_set_skey(sm_second_data_skey, "sm_ignore_case_c", global.ignore_case);

    if (skey) {
	edit->set_in_use = skey;
	sm_paint_skey(skey);
    } else {
	edit->set_in_use = sm_data_skey;
	sm_paint_skey(edit->set_in_use);
    }
    
    if (in_screen == NULL) {
	sm_message("SM_EDIT: called with NULL screen!");
	return(SM_ERROR);
    }
    
    /********************************************/
    /* MAKE SURE THAT SCREENS EXIST IF THEY ARE */
    /* MENTIONED AS KEY SCREENS, AND OPEN THEM. */
    /********************************************/
    
    if (_sm_open_master_screens(edit->base_screen, &edit->base_screen_has_masters))
	return(SM_ERROR);
    
    if (sm_user_screens()->tagname)
	if (edit->base_screen_has_masters) {
	    sm_message("SM_EDIT: Can't have a tagged file with keyed fields");
	    return(SM_ERROR);
	}		
    
    if (!(edit->gedout)) {
	sm_message("SM_EDIT: no return proposed!");
	return(SM_ERROR);
    }
    
    if (*sm_msg == 0)
	sm_record_num(edit->base_screen);
    
    /* An 'impossible' value to force a complete redisplay of a 
       scrolling screen */
    edit->last_data_on_screen = MAX_INDEX;

    sm_add_callback(sm_do_edit, edit);
    sm_edit_get_ready(SM_NOT_A_CALLBACK, edit);
    return(0);
}

#if 0
example of walking menus
int f1 ARG5(int, command, CHAR **, data, int *, index, int, row, int, col)
{
    return(command);
}

int f ARG5(int, command, CHAR **, data, int *, index, int, row, int, col)
{
    int i, ind = 0;

    switch(command) {
    case SM_RIGHT_WORD_C:
	(*index)++;
	break;

    case SM_NEXT_FIELD_C:
    case SM_NEXT_LINE_C:
    case SM_RIGHT_C:
	if (strcmp(data[*index], "Third") == 0) {
	    i = sm_selection(&test2, row, col, &ind, &f1);
	    if (i != SM_QUIT_C)
		sm_confirm(test2[ind]);
	    return(i);
	}

    default:
	break;
    }

    return(command);
}
#endif




/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
